package com.android.xmltool;

import java.util.*;
import java.util.zip.*;
import java.io.*;

import org.xmlpull.v1.*;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class DumpXml extends Activity {
    private static final String TAG = "DumpXml";
    private TextView pathField;
    private TextView statusField;

    private Map<String,String> namespaces;
    private Stack<NamespaceUriDepthPair> namespaceStack;

    @Override
    public void onCreate(Bundle savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
	pathField = (TextView) findViewById(R.id.path);
	statusField = (TextView) findViewById(R.id.status);
    }

    public void dumpXml(View view) {
	String path = pathField.getText().toString();
	if (path.trim().equals("")) {
	    statusField.setText(getString(R.string.path_needed));
	    return;
	}

	String[] pathParts = path.split("#");
	if (pathParts.length != 2) {
	    statusField.setText(getString(R.string.path_format));
	    return;
	}

	String apkPath = pathParts[0];
	String xmlPath = pathParts[1];
	if (!apkPath.startsWith("/")) { // package name
	    try {
		Context ctx = createPackageContext(apkPath, Context.CONTEXT_IGNORE_SECURITY);
		apkPath = ctx.getPackageCodePath();
	    }
	    catch (PackageManager.NameNotFoundException e) {
		statusField.setText(String.format(
			getString(R.string.package_not_found),
			apkPath)
		);
		return;
	    }
	}

	AssetManager am = getAssets();
	int cookie = am.addAssetPath(apkPath);
	if (cookie == 0) {
	    statusField.setText(String.format(getString(R.string.cannot_find), path));
	    return;
	}

	Log.d(TAG, "cookie = " + cookie);
	XmlResourceParser parser = null;
	try {
	    parser = am.openXmlResourceParser(cookie, xmlPath);
	}
	catch (Exception e) {
	    statusField.setText(String.format(getString(R.string.cannot_find), path));
	    return;
	}

	String dumpPath = null;
	try {
	    dumpPath = dumpXmlToSdCard(parser, xmlPath);
	}
	catch (Exception e) {
	    ByteArrayOutputStream o = new ByteArrayOutputStream();
	    e.printStackTrace(new PrintStream(o));
	    Log.e(TAG, o.toString());
	    statusField.setText(String.format(getString(R.string.failed), e.getMessage()));
	    return;
	}
	finally {
	    parser.close();
	}

	statusField.setText(String.format(getString(R.string.status), path, dumpPath));
    }

    private String dumpXmlToSdCard(XmlResourceParser p, String xmlPath) throws Exception {
	Log.d(TAG, "Parser: " + p);

	namespaces = new HashMap<String,String>();
	namespaces.put("http://schemas.android.com/apk/res/android", "android");
	namespaceStack = new Stack<NamespaceUriDepthPair>();

	File f = new File(Environment.getExternalStorageDirectory(), xmlPath);
	Log.d(TAG, "Output file: " + f.getPath());

	PrintWriter out = new PrintWriter(new FileWriter(f));

	int nextEvent = p.next();
	String version = (String) p.getProperty("http://xmlpull.org/v1/doc/properties.html#xmldecl-version");
	String standalone = (String) p.getProperty("http://xmlpull.org/v1/doc/features.html#xmldecl-standalone");
	String encoding = p.getInputEncoding();

	out.println(String.format("<?xml%s%s%s?>",
			(version == null) ? "" : " version=\"" + version + "\"",
			(standalone == null) ? "" : " standalone=\"" + standalone + "\"",
			(encoding == null) ? "" : " encoding=\"" + encoding + "\""));

	int depth = 1;
	boolean extraBlankLine = false;
	while (nextEvent != XmlPullParser.END_DOCUMENT) {
	    if (nextEvent == XmlPullParser.START_TAG) {
		depth = p.getDepth();
		String uri = p.getNamespace();
		pushNamespaceStack(uri, depth);
		String prefix = getNamespacePrefix(uri); 
		StringBuilder sb = new StringBuilder(getIndent(depth-1) + "<" + 
					(prefix.equals("") ? p.getName() : prefix + ":" + p.getName()));
		int attrCnt = p.getAttributeCount();
		if (attrCnt > 0) {
		    for (int i = 0; i < attrCnt; i++) {
			uri = p.getAttributeNamespace(i);
			pushNamespaceStack(uri, depth);
			prefix = getNamespacePrefix(uri);
			sb.append(String.format(
				(i == 0) ? " %s=\"%s\"" : "\n" + getIndent(depth) + "%s=\"%s\"",
				prefix.equals("") ? p.getAttributeName(i) : prefix + ":" + p.getAttributeName(i),
				p.getAttributeValue(i)
			));
		    }
		}

		for (NamespaceUriDepthPair pair: namespaceStack) {
		    if (pair.depth == depth) {
			sb.append(String.format("\n" + getIndent(depth) +"xmlns:%s=\"%s\"", 
				    getNamespacePrefix(pair.uri), 
				    pair.uri
				 )
			);
		    }
		}

		if (p.isEmptyElementTag()) {
		    sb.append("/>\n");
		    nextEvent = p.next();
		    popNamespaceStack(depth);
		    depth--;
		}
		else {
		    sb.append(">\n");
		}

		if (extraBlankLine) {
		    out.println();
		    extraBlankLine = false;
		}

		out.print(sb.toString());
	    }
	    else if (nextEvent == XmlPullParser.END_TAG) {
		out.print(getIndent(depth-1) + "</" + p.getName() + ">\n");
		extraBlankLine = true;
		popNamespaceStack(depth);
		depth--;
	    }
	    else if (nextEvent == XmlPullParser.TEXT) {
		out.print(p.getText());
	    }

	    nextEvent = p.next();
	}

	out.close();

	return f.getPath();
    }

    private String getIndent(int depth) {
	StringBuilder sb = new StringBuilder();
	for (int i = 0; i < depth; i++) {
	    sb.append("    ");
	}
	return sb.toString();
    }

    private char currentPrefix = 'a' - 1;
    private String getNextPrefix() {
	return new String(new char[] {++currentPrefix});
    }

    private String getNamespacePrefix(String namespaceUri) {
	String prefix = "";
	if ((namespaceUri != null) && !"".equals(namespaceUri)) {
	    prefix = namespaces.get(namespaceUri);
	    if (prefix == null) {
		prefix = getNextPrefix();
		namespaces.put(namespaceUri, prefix);
	    }
	}
	return prefix;
    }

    private static class NamespaceUriDepthPair {
	public String uri;
	public int depth;

	public NamespaceUriDepthPair(String uri, int depth) {
	    this.uri = uri;
	    this.depth = depth;
	}
    }

    private void pushNamespaceStack(String uri, int depth) {
	if (uri == null || uri.equals("")) {
	    return;
	}

	for (NamespaceUriDepthPair p: namespaceStack) {
	    if (p.uri.equals(uri)) {
		return;
	    }
	}

	namespaceStack.push(new NamespaceUriDepthPair(uri, depth));
    }

    private void popNamespaceStack(int depth) {
	while (!namespaceStack.empty() && namespaceStack.peek().depth == depth) {
	    namespaceStack.pop();
	}
    }
}
